﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;

namespace OpenPowerPoint.Collection
{
    /// <summary>
    /// A struct deploying all needed information about the changed property
    /// </summary>
    internal class SProperty : EventArgs
    {
        /// <summary>
        /// The name of the property
        /// </summary>
        public DependencyProperty Property { get; private set; }

        /// <summary>
        /// The old value of the property
        /// </summary>
        public object OldValue { get; private set; }

        /// <summary>
        /// The new value of the property
        /// </summary>
        public object NewValue { get; set; }

        /// <summary>
        /// A constructor setting all the required fields
        /// </summary>
        /// <param name="property">The name of the property</param>
        /// <param name="oldValue">The old value of the property</param>
        /// <param name="newValue">The new value of the property</param>
        public SProperty(DependencyProperty property, object oldValue, object newValue)
        {
            Property = property;
            OldValue = oldValue;
            NewValue = newValue;
        }

        public bool IsRemove()
        {
            return OldValue != null && NewValue == null;
        }

        public bool IsAlter()
        {
            return OldValue != null && NewValue != null;
        }

        public bool IsAdd()
        {
            return OldValue == null && NewValue != null;
        }

        public bool IsClear()
        {
            return OldValue is IEnumerable && NewValue == null;
        }
    }

    /// <summary>
    /// Implements Cascading Notifications for Lists
    /// </summary>
    /// <typeparam name="T">The base class to use</typeparam>
    internal class NotifyableList<T> : ObservableCollection<T>, INotify where T : INotify
    {
        /// <summary>
        /// The delegate for a changed property
        /// </summary>
        /// <param name="sender">The sender object which invoced the event</param>
        /// <param name="property">The name of the property which has changed</param>
        public delegate void PropertyChangedDelegate(object sender, SProperty e);

        /// <summary>
        /// Gets invoked when a child item invokes PropertyChanged
        /// </summary>
        public new event NotifyableList<INotify>.PropertyChangedDelegate PropertyChanged;

        /// <summary>
        /// Call handler for PropertyChanged
        /// </summary>
        /// <param name="sender">The initial sender object</param>
        /// <param name="property">The property which has changed</param>
        protected virtual void OnPropertyChanged(object sender, SProperty property)
        {
            NotifyableList<INotify>.PropertyChangedDelegate handler = PropertyChanged;
            if (handler != null) handler(sender, property);
        }

        //This region contains all overrides of adding or removing methods and adds the property changed handler to them

        #region Overrides

        public NotifyableList(IEnumerable<T> collection)
            : base(collection.ToList())
        {
            foreach (T item in this)
            {
                item.PropertyChanged += OnPropertyChanged;
            }
        }

        public NotifyableList()
        {
        }

        ~NotifyableList()
        {
            foreach (T item in this)
            {
                item.PropertyChanged -= OnPropertyChanged;
            }
        }

        protected override void InsertItem(int index, T item)
        {
            var property = new SProperty(null, null, item);
            OnPropertyChanged(this, property);
            if (property.NewValue == null) return;

            base.InsertItem(index, item);
            item.PropertyChanged += OnPropertyChanged;
        }

        protected override void ClearItems()
        {
            var property = new SProperty(null, this.ToList(), null);
            OnPropertyChanged(this, property);
            if (property.NewValue != null) return;

            foreach (var item in this)
                item.PropertyChanged -= OnPropertyChanged;

            base.ClearItems();
        }

        protected override void RemoveItem(int index)
        {
            var property = new SProperty(null, this[index], null);
            OnPropertyChanged(this, property);
            if (property.NewValue != null) return;

            this[index].PropertyChanged -= OnPropertyChanged;
            base.RemoveItem(index);
        }

        protected override void SetItem(int index, T item)
        {
            var property = new SProperty(null, this[index], item);
            OnPropertyChanged(this, property);

            this[index].PropertyChanged -= OnPropertyChanged;
            item.PropertyChanged += OnPropertyChanged;

            base.SetItem(index, (T)property.NewValue);
        }

        public void AddRange(IEnumerable<T> enumerable)
        {
            foreach (T item in enumerable)
            {
                Add(item);
            }
        }

        public void InsertRange(int index, IEnumerable<T> items)
        {
            var collection = items.ToList();

            for (int i = collection.Count - 1; i >= 0; i--)
            {
                Insert(index, collection[i]);
            }
        }

        public void RemoveAll(Predicate<T> items)
        {
            var list = FindAll(items);

            foreach (var item in list)
                Remove(item);
        }

        private IEnumerable<T> FindAll(Predicate<T> items)
        {
            return this.Where(items.Invoke).ToList();
        }

        public void RemoveRange(int index, int count)
        {
            var items = GetRange(index, count);

            foreach (var item in items)
                Remove(item);
        }

        private IEnumerable<T> GetRange(int index, int count)
        {
            for (int i = index; i < index + count; i++)
            {
                yield return this[i];
            }
        }

        #endregion
    }
}